Germinate's Blog

IE浏览器中文参数导致400 Bad Request问题研究

问题描述

本人在使用Jboss-eap-7.1(内核是Wildfly)时,向服务器发送带中文url参数的Get请求时,服务器会返回400错误。而且应用根本接收不到该请求的信息,在连接层面就被拦截了,没有将请求转给应用处理。不止是Wildfly,tomcat的7,8版本也存在一样的情况。

问题探究

经过实验,发现IE浏览器6, 8, 11版本均存在该问题,但chrome浏览器正常,微软的Edge浏览器也是正常的。除了中文,如果参数含有‘<’, ‘{‘等特殊字符时,IE浏览器也会报400错误。经过查找资料和抓包分析,发现是编码问题导致了两个浏览器的结果不同,但不是大家一般认为的字符集的原因。下面是不同浏览器针对GET请求URL编码情况的分析。对于GET URL http://example.com/test?lover=樊

  • chrome浏览器先使用UTF-8字符集编码URL中的所有字符,再使用 %编码(Percent-Encoding)将字节转换为%HH(%+两个十六进制数字)的形式,这样可以保证URL中全部是标准ASCII字符。樊的UTF-8编码为{E6, A8, 8A},实际发送的请求是 http://example.com/test?lover=%E6%A8%8A
  • IE浏览器会使用GBK字符集编码URL中的参数,但不会转换为%编码。樊的GBK编码为{B7, AE} 。实际发送的请求时,直接将樊字对应的GBK字节作为请求的内容。

以上分析也许不太容易理解,我们再从字节的角度详细解释一下。
chrome请求抓包内容
以上是chrome浏览器发送请求的抓包内容,尾部的参数是=%E6%A8%8A,实际发送的字节为{ 3D, 25, 45, 36, 25, 41, 38, 25, 38, 41, 38, 41 }。’=’ 的ASCII码为3D,’%’ 的ASCII码为25,’E’ 的ASCII码为45,以此类推。
IE请求抓包内容
以上是IE浏览器发送请求的抓包内容,尾部的参数是=樊,实际发送的字节为{ 3D, B7, AE }。B7和AE都是超出RFC 3986规范允许的字节。

%编码是使用字符集编码后,再将每个超出规范允许范围的字节转换为 %+字节对应的十六进制 的形式。
另外,IE中有一个选项,“发送UTF-8 URL”。选中之后,参数还是以GBK格式发送。这个仅仅对URL路径中的字符生效,选中就是UTF-8,不勾选就是GBK。但对于URL参数没有作用,永远都使用GBK字符集。IE会对路径中的字符做%编码,URL参数不做。

根本原因

所以问题的根本原因是IE没有遵循RFC 3986规范,未对URL参数使用 %编码,而是直接将字符对应的字节码发送到服务器。Wildfly服务器端的安全机制对此类URL做了拦截。它在该版本做了更新,遵循RFC 3986规范,对于url中含有非法字符的请求,直接予以拒绝,并返回400错误。相关代码摘录如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private static final boolean[] ALLOWED_TARGET_CHARACTER = new boolean[256];
………… ………… …………
for(int i = 0; i < 256; ++i) {
if(i < 32 || i > 126) {
ALLOWED_TARGET_CHARACTER[i] = false;
} else {
switch ((char)i) {
case '\"':
case '#':
case '<':
case '>':
case '\\':
case '^':
case '`':
case '{':
case '|':
case '}':
ALLOWED_TARGET_CHARACTER[i] = false;
break;
default:
ALLOWED_TARGET_CHARACTER[i] = true;
}
}
}
………… ………… …………
while (buffer.hasRemaining()) {
char next = (char) (buffer.get() & 0xFF);
if(!allowUnescapedCharactersInUrl && !ALLOWED_TARGET_CHARACTER[next]) {
throw new BadRequestException(UndertowMessages.MESSAGES.invalidCharacterInRequestTarget(next));
}
………… ………… …………
}

可以看出,对于ASCII码在32-125之外的字符,是不允许的,直接抛出异常。对于该范围内的字符,有10个不允许出现。
根据代码,可以解释上述中文的问题。由GBK编码规范可知,字符的第一个字节肯定大于127,不被服务器接受,所以返回了400错误。
所以,问题并不是字符集导致的,而是%编码导致的。即使使用GBK编码中文字符,只要将字符的每个字节都转换为%HH的形式,依然能正确发送请求。至于可能导致的乱码问题,也有解决方式。但最好根据规范使用UTF-8。
根据参考资料2显示,Tomcat7,8也严格执行了RFC 3986规范,阻止不安全字符。但我认为该文章对原因的分析是不准确的。

Jboss下解决方案

根据UNDERTOW-1185的ISSUE,Undertow在该版本做了更新,增加了一个开关选项 ALLOW_UNESCAPED_CHARACTERS_IN_URL ,当配置为true时,就不再根据规范限制url中的字符。相应地,Jboss eap根据JBEAP-13710的ISSUE,将在JBEAP-13744更新Undertow版本,预计会在Jboss-eap-7.1.1版本中见到新的配置项。
但如果有条件,最好还是使用encodeUrl()编码一下,可以避免很多问题。与之相关的资料非常多,本文不再赘述。

参考资料

  1. URL编码问题&乱码根源
  2. IE6-IE11 Get请求参数带中文tomcat 返回400错误并显示:Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986